package org.scohen.juploadr.ui;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.DeviceData;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Pattern;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.Transform;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * Instructions on how to use the Sleak tool.
 * 
 * A) If you have access to the place where the display is created (most
 * standalone applications):
 * 
 * Modify the creation of the Display as follows:
 * 
 * DeviceData data = new DeviceData(); data.tracking = true; Display display =
 * new Display(data); Sleak sleak = new Sleak(); sleak.open(); Shell shell = new
 * Shell(display);
 * 
 * B) If you do not have access to the place where the display is created
 * (Eclipse):
 * 
 * 1) This tool will be referenced from the Device class so it needs to be
 * copied into the same project. Copy the package org.eclipse.swt.internal.tools
 * to a source folder in the org.eclipse.swt project.
 * 
 * 2) This tool relies on debug information collected by the Device class.
 * Modify the Device class to turn DEBUG on:
 * 
 * public static boolean DEBUG = true;
 * 
 * 3) The Sleak tool must be launched from the constructor of the Device class.
 * Add the following lines to the end of the Device constructor:
 * 
 * if (this.getClass().getName().equals("org.eclipse.swt.widgets.Display")) {
 * org.eclipse.swt.internal.tools.Sleak sleak = new
 * org.eclipse.swt.internal.tools.Sleak(); sleak.open(); }
 * 
 * 4) Create a new swt.jar for the platform you wish to test. This is done by
 * running the build.xml script in the appropriate windowing system fragment.
 * Select the ws/$ws/swt.jar target.
 * 
 * 5) Copy the new swt.jar to the appropriate location. When using Sleak with
 * Eclipse, this is eclipse/org.eclipse.swt.<ws>_<version>/ws/<ws>
 * 
 * 6) Launch your application and the Sleak GUI will be displayed. Click on the
 * Snap button to capture the current resource state. Click on the Diff button
 * to compare the current state to the previous snapshot state.
 * 
 */
public class Sleak {
    private static final char DELIMETER = ':';
    Display display;
    SashForm parent;
    List list;
    Canvas canvas;
    Button start, stop;
    Text text;
    List types;
    Text filter;
    Object[] oldObjects = new Object[0];
    Error[] oldErrors = new Error[0];
    Object[] objects = new Object[0];
    Error[] errors = new Error[0];
    private Button checkDuplicates;
    private List dupes;
    private int[] previousWeights;
    private Object paintHolder;
    private int[] holderIndex;
    private int[] holder = new int[] { 0 };
    private Label status;
    private SashForm sashForm;
    private int previousTotal;
    private List dupeDetails;
    
    static Comparator sortResources = new Comparator<ResourceAndCount>() {
        public int compare(ResourceAndCount r1, ResourceAndCount r2) {
            int diff = r2.count - r1.count;
            return diff == 0 ? -1 : diff;
        }
    };

    public static void main(String[] args) {
        DeviceData data = new DeviceData();
        data.tracking = true;
        data.debug = true;
        Display display = new Display(data);
        Sleak sleak = new Sleak();
        Shell shell = sleak.open();
        makeLeak(display);
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
    }

    private static void makeLeak(final Display display) {
        display.timerExec(200, new Runnable() {
            public void run() {
                // make dups
                new Color(display, 255, 255, 1);
                new Color(display, 255, 255, 1);
                new Color(display, 255, 255, 1);
                new Font(display, "Arial", 12, SWT.BOLD);
                new Font(display, "Arial", 12, SWT.BOLD);
                new Image(display, 16, 16);
                new Image(display, 16, 16);
                new Image(display, 300, 300);
                new Image(display, 300, 300);

                for (int i = 0; i < 20; i++)
                    new Color(display, 0, i, 2);
                for (int i = 0; i < 5; i++)
                    new Font(display, "Courier", i, SWT.NORMAL);
                for (int i = 0; i < 5; i++)
                    new Region(display);
            }
        });
        display.timerExec(5000, new Runnable() {
            public void run() {
                // make dups
                new Color(display, 255, 255, 1);
                display.timerExec(5000, this);
            }
        });
    }

    public Shell open() {
        Display display = Display.getCurrent();
        Shell shell = new Shell(display);
        shell.setText("S-Leak");
        open(shell);
        Point size = shell.getSize();
        shell.setSize(size.x / 2, size.y / 2);
        shell.open();
        return shell;
    }

    public void open(Composite parent2) {
        parent2.setLayout(new FormLayout());
        sashForm = new SashForm(parent2, SWT.HORIZONTAL);
        parent = sashForm;
        Composite column1 = new Composite(sashForm, SWT.NONE);
        FormLayout formLayout = new FormLayout();
        formLayout.marginLeft = 2;
        formLayout.marginRight = 2;
        column1.setLayout(formLayout);

        final SashForm column2Duplicates = new SashForm(sashForm, SWT.VERTICAL);
        column2Duplicates.setLayout(new FormLayout());

        SashForm column3 = new SashForm(sashForm, SWT.VERTICAL);

        sashForm.setWeights(new int[] { 20, 0, 50 });

        this.display = Display.getCurrent();
        list = new List(column1, SWT.BORDER | SWT.V_SCROLL | SWT.MULTI);
        list.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                int selectionIndex = list.getSelectionIndex();
                holder[0] = selectionIndex;
                holderIndex = holder;
                if (selectionIndex == -1) {
                    paintHolder = null;
                    return;
                }
                paintHolder = objects[selectionIndex];
                refreshObject();
            }
        });

        canvas = new Canvas(column3, SWT.BORDER);
        canvas.addListener(SWT.Paint, new Listener() {
            public void handleEvent(Event event) {
                paintCanvas(event);
            }
        });
        text = new Text(column3, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
        column3.setWeights(new int[] { 20, 80 });

        start = new Button(column1, SWT.PUSH);
        start.setText("Snap");
        start.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                refreshAll();
            }
        });
        stop = new Button(column1, SWT.PUSH);
        stop.setText("Diff");
        stop.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                refreshDifference(false);
            }
        });

        filter = new Text(column1, SWT.BORDER);
        types = new List(column1, SWT.BORDER | SWT.V_SCROLL | SWT.MULTI);
        types.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                // refreshDifference();
            }
        });
        checkDuplicates = new Button(column1, SWT.CHECK);
        checkDuplicates.setText("Sort duplicates");
        checkDuplicates.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                showHideDuplicates(sashForm, column2Duplicates);
            }
        });
        dupes = new List(column2Duplicates, SWT.BORDER | SWT.V_SCROLL | SWT.MULTI);
        dupes.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                selectDupes();
            }
        });
        dupeDetails = new List(column2Duplicates, SWT.BORDER | SWT.V_SCROLL | SWT.MULTI);
        dupeDetails.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                selectDupeDetails();
            }
        });
        status = new Label(parent2, SWT.LEFT | SWT.SHADOW_ETCHED_IN | SWT.BORDER);

        column2Duplicates.setVisible(false);
        setupLayout();
    }

    protected void selectDupeDetails() {
        int selected = dupeDetails.getSelectionIndex();
        java.util.List duplicates = (java.util.List) dupes.getData("duplicates");
        ResourceAndCount resourceAndCount = (ResourceAndCount) duplicates.get(dupes.getSelectionIndex());
        int resourceIndex = resourceAndCount.indexes[selected];
        paintHolder = objects[resourceIndex];
        holder[0] = resourceIndex;
        holderIndex = holder;
        refreshObject();
    }

    protected void selectDupes() {
        int index = dupes.getSelectionIndex();
        java.util.List duplicates = (java.util.List) dupes.getData("duplicates");
        ResourceAndCount resourceAndCount = (ResourceAndCount) duplicates.get(index);
        paintHolder = resourceAndCount.resource;
        holderIndex = resourceAndCount.indexes;
        list.setSelection(holderIndex);
        refreshObject();
        dupeDetails.removeAll();
        for (int i = 0; i < resourceAndCount.indexes.length; i++) {
            int indexIntoList = resourceAndCount.indexes[i];
            if (indexIntoList == -1)
                return;
            dupeDetails.add(objectName(objects[indexIntoList]));
        }
    }

    private void setupLayout() {
        FormData data = new FormData();
        data.bottom = new FormAttachment(100, -2);
        data.right = new FormAttachment(100, -2);
        data.left = new FormAttachment(0, 2);
        status.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(0, 2);
        data.left = new FormAttachment(0, 2);
        data.right = new FormAttachment(100, -2);
        data.bottom = new FormAttachment(status, -2);
        sashForm.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(0, 2);
        data.left = new FormAttachment(0, 0);
        data.right = new FormAttachment(100, -2);
        start.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(start, 2);
        data.left = new FormAttachment(0, 0);
        data.right = new FormAttachment(start, 0, SWT.RIGHT);
        stop.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(stop, 2);
        data.left = new FormAttachment(0, 0);
        data.right = new FormAttachment(start, 0, SWT.RIGHT);
        filter.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(filter, 2);
        data.left = new FormAttachment(0, 0);
        data.right = new FormAttachment(start, 0, SWT.RIGHT);
        data.bottom = new FormAttachment(75);
        list.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(list, 2);
        data.left = new FormAttachment(0, 0);
        data.right = new FormAttachment(start, 0, SWT.RIGHT);
        data.bottom = new FormAttachment(checkDuplicates, -2);
        types.setLayoutData(data);

        data = new FormData();
        data.left = new FormAttachment(0, 2);
        data.right = new FormAttachment(start, 0, SWT.RIGHT);
        data.bottom = new FormAttachment(100, -2);
        checkDuplicates.setLayoutData(data);

        data = new FormData();
        data.top = new FormAttachment(0, 2);
        data.left = new FormAttachment(0, 2);
        data.right = new FormAttachment(100, -2);
        data.bottom = new FormAttachment(100, -2);
        dupes.setLayoutData(data);
    }

    void refreshTypes(Object[] newObjects) {
        Map<Class, int[]> counts = new HashMap<Class, int[]>(10);
        for (int i = 0; i < newObjects.length; i++) {
            Object object = newObjects[i];
            int[] cnt = (int[]) counts.get(object.getClass());
            if (cnt == null) {
                cnt = new int[1];
                counts.put(object.getClass(), cnt);
            }
            cnt[0]++;
        }

        Class[] classes = (Class[]) counts.keySet().toArray(new Class[counts.size()]);
        Arrays.sort(classes, classComparator);

        String[] selectedItems = types.getSelection();
        int[] selIdxs = new int[selectedItems.length];
        int selIdx = 0;
        types.removeAll();
        for (int i = 0; i < classes.length; ++i) {
            int[] cnt = (int[]) counts.get(classes[i]);
            String className = classes[i].getName();
            int idx = className.lastIndexOf('.');
            if (idx != -1)
                className = className.substring(idx + 1);
            types.add(className + DELIMETER + " " + cnt[0]);
            for (int j = 0; j < selectedItems.length; ++j) {
                if (selectedItems[j].startsWith(className)) {
                    selIdxs[selIdx++] = i;
                    break;
                }
            }
        }
        for (; selIdx < selIdxs.length; ++selIdx)
            selIdxs[selIdx] = -1;
        types.select(selIdxs);
    }

    static Comparator classComparator = new Comparator<Class>() {
        public int compare(Class o1, Class o2) {
            return o1.getName().compareTo(o2.getName());
        }
    };

    static class ResourceAndCount {
        Object resource;
        int count;
        private int[] indexes;
        private int hashCode = -1;

        public ResourceAndCount(Object resource) {
            this.resource = resource;
            this.count = 0;
        }

        public void inc() {
            count++;
        }

        public int hashCode() {
            if (hashCode == -1)
                hashCode = hashForResource();
            return hashCode;
        }

        private int hashForResource() {
            if (resource instanceof Color) {
                return hashColor(resource);
            } else if (resource instanceof Cursor) {
                // don't see a way to compare cursors
                return resource.hashCode();
            } else if (resource instanceof Font) {
                return fontHash(resource).hashCode();
            } else if (resource instanceof GC) {
                // not sure what to do with GCs
                return resource.hashCode();
            } else if (resource instanceof Image) {
                return imageHash(resource);
            } else if (resource instanceof Path) {
                // not sure
            } else if (resource instanceof Pattern) {
                // not sure
            } else if (resource instanceof Region) {
                // not sure
            } else if (resource instanceof TextLayout) {
                // not sure
            } else if (resource instanceof Transform) {
                // not sure
            }
            return -1;
        }

        private int hashColor(Object aColor) {
            if (aColor == null)
                return 1;
            Color color = (Color) aColor;
            return color.getRGB().hashCode();
        }

        private int imageHash(Object resource2) {
            Image image = (Image) resource2;
            if (image.isDisposed())
                return -1;
            byte[] bs = image.getImageData().data;
            int hash = 0, multiplier = 1;
            for (int i = 0; i < bs.length; i++) {
                hash += bs[i] * multiplier;
                int shifted = multiplier << 5;
                multiplier = shifted - multiplier;
            }
            Rectangle bounds = image.getBounds();
            return hash * bounds.width * 23 * bounds.height;
        }

        private boolean equalsForObject(ResourceAndCount other) {
            Object resourcea = this.resource;
            Object resourceb = other.resource;
            if (resourcea.getClass() != resourceb.getClass())
                return false;
            if (resourcea instanceof Color) {
                return ((Color) resourcea).getRGB().equals(((Color) resourceb).getRGB());
            } else if (resourcea instanceof Cursor) {
                // don't see a way to compare cursors
                return resourcea.equals(resourceb);
            } else if (resourcea instanceof Font) {
                return fontHash(resourcea).equals(fontHash(resourceb));
            } else if (resourcea instanceof GC) {
                // not sure what to do with GCs
                return resourcea.equals(resourceb);
            } else if (resourcea instanceof Image) {
                return this.hashCode() == other.hashCode();
            } else if (resourcea instanceof Path) {

            } else if (resourcea instanceof Pattern) {

            } else if (resourcea instanceof Region) {

            } else if (resourcea instanceof TextLayout) {

            } else if (resourcea instanceof Transform) {

            }
            return false;
        }

        private String fontHash(Object resourcea) {
            FontData[] array = ((Font) resourcea).getFontData();
            StringBuffer buffer = new StringBuffer();
            for (int i = 0; i < array.length; i++) {
                FontData data = array[i];
                String style = "NORMAL";
                int bits = data.getStyle();
                if (bits != 0) {
                    if ((bits & SWT.BOLD) != 0)
                        style = "BOLD ";
                    if ((bits & SWT.ITALIC) != 0)
                        style += "ITALIC";
                }
                buffer.append(data.getName());
                buffer.append(data.getHeight());
                buffer.append(style);
            }
            return buffer.toString();
        }

        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final ResourceAndCount other = (ResourceAndCount) obj;
            if (resource == null) {
                if (other.resource != null)
                    return false;
            } else if (!equalsForResource(other))
                return false;
            return true;
        }

        private boolean equalsForResource(ResourceAndCount other) {
            if (this.hashCode() != other.hashCode())
                return false;
            return equalsForObject(other);
        }

        public void addIndex(int toAdd) {
            if (indexes == null) {
                indexes = new int[4];
                Arrays.fill(indexes, -1);
            }
            int length = indexes.length, index = length - 1;
            while (index >= 0) {
                if (indexes[index] != -1)
                    break;
                --index;
            }
            index++;
            if (index == length) {
                int[] newIndexes = new int[length + 4];
                Arrays.fill(newIndexes, -1);
                System.arraycopy(indexes, 0, newIndexes, 0, length);
                indexes = newIndexes;
            }
            indexes[index] = toAdd;
        }
    }

    void refreshDifference(boolean showDiff) {
        DeviceData info = display.getDeviceData();
        if (!info.tracking) {
            MessageBox dialog = new MessageBox(parent.getShell(), SWT.ICON_WARNING | SWT.OK);
            dialog.setText(parent.getShell().getText());
            dialog.setMessage("Warning: Device is not tracking resource allocation");
            dialog.open();
        }
        Object[] newObjects = info.objects;
        Error[] newErrors = info.errors;
        Object[] diffObjects = new Object[newObjects.length];
        Error[] diffErrors = new Error[newErrors.length];

        int count = 0;

        String[] selectedTypes = types.getSelection();
        if (selectedTypes.length == 0 || selectedTypes.length == types.getItemCount()) {
            // if we select nothing or everything, make filtering easy
            selectedTypes = null;
        }

        String filterText = filter.getText().trim();
        if (filterText.length() == 0)
            filterText = null;

        String[] typeNames = null;
        if (selectedTypes != null) {
            typeNames = new String[selectedTypes.length];
            for (int i = 0; i < selectedTypes.length; i++) {
                int delimeterIndex = selectedTypes[i].lastIndexOf(DELIMETER);
                typeNames[i] = delimeterIndex == -1 ? selectedTypes[i] : selectedTypes[i].substring(0,
                        delimeterIndex);
            }
        }
        for (int i = 0; i < newObjects.length; i++) {
            int index = 0;
            while (index < oldObjects.length) {
                if (newObjects[i] == oldObjects[index])
                    break;
                index++;
            }
            if (index == oldObjects.length) {
                if (typeNames != null) {
                    boolean found = false;
                    for (int j = 0; j < typeNames.length; ++j) {
                        if (newObjects[i].getClass().getName().indexOf(typeNames[j]) > -1) {
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                        continue;
                }

                if (filterText != null) {
                    // filter
                    // iterate over stack traces - check class, method and the
                    // span
                    StackTraceElement[] stes = newErrors[i].getStackTrace();
                    boolean found = false;
                    for (int j = 0; j < stes.length; ++j) {
                        if (stes[j].getClassName().indexOf(filterText) > 0) {
                            found = true;
                            break;
                        }

                    }
                    if (!found) {
                        continue;
                    }
                }

                diffObjects[count] = newObjects[i];
                diffErrors[count] = newErrors[i];
                count++;
            }
        }
        objects = new Object[count];
        errors = new Error[count];
        System.arraycopy(diffObjects, 0, objects, 0, count);
        System.arraycopy(diffErrors, 0, errors, 0, count);
        list.removeAll();
        text.setText("");
        canvas.redraw();
        for (int i = 0; i < objects.length; i++) {
            list.add(objectName(objects[i]));
        }
        refreshTypes(newObjects);
        String statusMessage = "Total: " + objects.length;
        int diff = objects.length - previousTotal;
        if (showDiff && previousTotal > 0 && diff != 0) {
            statusMessage += " ";
            if (diff > 0)
                statusMessage += "+";
            statusMessage += diff;
        }
        previousTotal = objects.length;
        status.setText(statusMessage);
    }

    String objectName(Object object) {
        String string = object.toString();
        int index = string.lastIndexOf('.');
        if (index == -1)
            return string;
        return string.substring(index + 1);
    }

    void toggleStackTrace() {
        refreshObject();
    }

    void paintCanvas(Event event) {
        canvas.setCursor(null);
        if (paintHolder == null)
            return;
        Object object = paintHolder;
        GC gc = event.gc;
        if (object instanceof Color) {
            if (((Color) object).isDisposed())
                return;
            gc.setBackground((Color) object);
            gc.fillRectangle(canvas.getClientArea());
            return;
        }
        if (object instanceof Cursor) {
            if (((Cursor) object).isDisposed())
                return;
            canvas.setCursor((Cursor) object);
            return;
        }
        if (object instanceof Font) {
            if (((Font) object).isDisposed())
                return;
            gc.setFont((Font) object);
            FontData[] array = gc.getFont().getFontData();
            String string = "";
            String lf = text.getLineDelimiter();
            for (int i = 0; i < array.length; i++) {
                FontData data = array[i];
                String style = "NORMAL";
                int bits = data.getStyle();
                if (bits != 0) {
                    if ((bits & SWT.BOLD) != 0)
                        style = "BOLD ";
                    if ((bits & SWT.ITALIC) != 0)
                        style += "ITALIC";
                }
                string += data.getName() + " " + data.getHeight() + " " + style + lf;
            }
            gc.drawString(string, 0, 0);
            return;
        }
        // NOTHING TO DRAW FOR GC
        // if (object instanceof GC) {
        // return;
        // }
        if (object instanceof Image) {
            Image image = (Image) object;
            if (!image.isDisposed())
                gc.drawImage((Image) object, 0, 0);
            return;
        }
        if (object instanceof Path) {
            Path path = (Path) object;
            if (!path.isDisposed())
                gc.drawPath(path);
            return;
        }
        if (object instanceof Pattern) {
            Pattern pattern = (Pattern) object;
            if (!pattern.isDisposed()) {
                gc.setBackgroundPattern(pattern);
                gc.fillRectangle(canvas.getClientArea());
            }
            return;
        }
        if (object instanceof Region) {
            if (((Region) object).isDisposed())
                return;
            String string = ((Region) object).getBounds().toString();
            gc.drawString(string, 0, 0);
            return;
        }
        if (object instanceof TextLayout) {
            TextLayout layout = (TextLayout) object;
            if (!layout.isDisposed()) {
                layout.draw(gc, 0, 0);
            }
            return;
        }
        if (object instanceof Transform) {
            Transform xform = (Transform) object;
            if (!xform.isDisposed()) {
                String s = xform.toString();
                gc.drawString(s, 0, 0);
            }
            return;
        }

    }

    void refreshObject() {
        if (paintHolder == null)
            return;
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        PrintStream s = new PrintStream(stream);
        errors[holderIndex[0]].printStackTrace(s);
        text.setText(stream.toString());
        canvas.redraw();
        text.getParent().layout();
    }

    void refreshAll() {
        oldObjects = new Object[0];
        oldErrors = new Error[0];
        refreshDifference(true);
        oldObjects = objects;
        oldErrors = errors;
    }

    private void showHideDuplicates(final SashForm sashForm, final Composite column2) {
        column2.setVisible(checkDuplicates.getSelection());
        int[] weights = sashForm.getWeights();
        int[] newWeights = sashForm.getWeights();
        if (checkDuplicates.getSelection()) {
            if (previousWeights == null || Math.abs(weights[0] - previousWeights[0]) > 30) {
                // column0 has been resized, honor column1 instead of using the
                // cached values
                int sum = weights[0] + weights[1] + weights[2];
                int half = (sum - weights[0]) / 2;
                newWeights[1] = half;
                newWeights[2] = half;
            } else
                newWeights = previousWeights;
            dupes.removeAll();
            // dupes.add("computing...");
            // new Thread(new Runnable() {
            // public void run() {
            // computeDuplicates();
            // }
            // }).start();
            computeDuplicates();
        } else {
            previousWeights = weights;
            int sum = weights[0] + weights[1] + weights[2];
            int weight0 = (int) (sum * (weights[0] / (float) sum));
            newWeights[0] = weight0;
            newWeights[1] = 0;
            newWeights[2] = sum - weight0;
        }
        column2.getParent().layout();
        sashForm.setWeights(newWeights);
    }

    protected void computeDuplicates() {
        Map<ResourceAndCount,ResourceAndCount> dups = new HashMap<ResourceAndCount,ResourceAndCount>();
        for (int i = 0; i < objects.length; i++) {
            Object object = objects[i];
            ResourceAndCount lookup = new ResourceAndCount(object);
            ResourceAndCount resourceAndCount = (ResourceAndCount) dups.get(lookup);
            if (resourceAndCount == null) {
                resourceAndCount = lookup;
                dups.put(resourceAndCount, resourceAndCount);
            }
            resourceAndCount.addIndex(i);
            resourceAndCount.inc();
        }
        java.util.List<ResourceAndCount> duplicates = new ArrayList<ResourceAndCount>();
        for (Iterator iterator = dups.values().iterator(); iterator.hasNext();) {
            ResourceAndCount resourceAndCount = (ResourceAndCount) iterator.next();
            if (resourceAndCount.count > 1) {
                duplicates.add(resourceAndCount);
            }
        }
        Collections.sort(duplicates, sortResources);
        dupes.setData("duplicates", duplicates);
        for (Iterator iterator = duplicates.iterator(); iterator.hasNext();) {
            ResourceAndCount resourceAndCount = (ResourceAndCount) iterator.next();
            dupes.add(objectName(resourceAndCount.resource) + DELIMETER + " " + resourceAndCount.count);
        }
    }
}
